// CadTool.cpp
//
#include "CadTool.h"

#include "CadEngine.h"

#include "MemCheck.h"

// ------------------------------------------------------------------------------------------------
CadTool::CadTool( const std::string &name, int numPoints/*, CadTool* next */) 
:  _name(name),
  _totPoints(numPoints),
  _manager(0),
  _waitConfirm(false),
//  _nextTool(next),
  DISCARD_THRESHOLD(8),
  SELECTION_THRESHOLD(8),
  COLLAPSE_THRESHOLD(32)
{
	reset();
}

// ------------------------------------------------------------------------------------------------
CadTool::~CadTool()
{}

// ------------------------------------------------------------------------------------------------
//void Action::setPointFromGUI( const IDSReal2D &p, int index,bool GEO)
//{
//	selIndex = index;
//
//	if( nPoints <= index )
//		nPoints = index+1;
//
//	IDSReal2D pointAction = p;
//
//	if (isGeographic())
//	{
//		if(!GEO)
//		{
//			try
//			{
//				pointAction = CoordinateManager::getInstance().getGeoCoord( p );
//			}
//			catch(...){}
//		}
//	}
//	else
//	{
//		if(GEO)
//		{
//			try
//			{
//				IDS::BaseGeometry::PointEllipsoidic2D pEll(p[0],p[1]);
//				pointAction = CoordinateManager::getInstance().getPointFor2DView(pEll);
//			}
//			catch(...){}
//		}
//	}
//
//	status    = Drag;
//	updateGUI = false;
//	setPoint( pointAction, false, selIndex );
//}


// ------------------------------------------------------------------------------------------------
//void Action::setPointFromGUI( const double &x,const double &y, const double &lon, const double &lat, int index)
//{
//	selIndex = index;
//
//	if( nPoints <= index )
//		nPoints = index+1;
//
//	if( isGeographic() )
//		setPoint( IDSReal2D(lon,lat), false, selIndex );
//	else
//		setPoint( IDSReal2D(x,y), false, selIndex );
//
//	status    = Drag;
//	updateGUI = false;
//	//validate();
//}
//
// ------------------------------------------------------------------------------------------------
int CadTool::setPoint(const OpenGL::Math::dvec3 &p,bool force,int index )
{
	int idx = index >=0 ? index : _nPoints;

	if( idx == _totPoints && _totPoints > 0 )
		idx--;

	if( idx < 0 )
		return index<0 ? _nPoints-1 : index;

	_points[idx]      = p;

	bool validPoint = true;
	//if( isGeographic() )
	//{
	//	try
	//	{
	//		IDS::BaseGeometry::PointEllipsoidic2D pEll(p[0],p[1]);
	//		pointsCHART[idx] = CoordinateManager::getInstance().getPointFor2DView(pEll);
	//	}
	//	catch(...){validPoint = false;}
	//}

	if( /*isScreenSpace() &&*/ validPoint )
		_points[idx] = p;

	if(index <0 && validPoint) // want to insert a new point
	{
		// get closest point in chart space
 		const int closest = getClosestPoint( _points[idx] );

		// insert only if its distance is big enough from other points
		if( (force || closest < 0) 
			&& !_pointsLocked[idx] )
			_nPoints++;
	}

	return index<0 ? _nPoints-1 : index;
}

// ------------------------------------------------------------------------------------------------
void CadTool::setPointValue( const OpenGL::Math::dvec3 &point,int index )
{
	if( index < 0 )
		return;
	
	if( _nPoints == 0 )
		_nPoints = _totPoints;

	//if( _chained )
	//	_nPoints--;
	//if( index <= _nPoints )
	//	_nPoints = index+1;

	_points[index] = point;
	//_selIndex = -1;
}

// ------------------------------------------------------------------------------------------------
void CadTool::setAttributes( const std::string &attrib )
{
	_objectAttributes = attrib;
}

//
//// ------------------------------------------------------------------------------------------------
//IDS::BaseGeometry::Point2D Action::getPoint2D(int i)
//{
//	BaseGeometry::Point2D point;
//	if (i >= 0 && i < getNumCollectedPoints())
//	{
//		if (isGeographic())
//			point = BaseGeometry::makePoint(points.at(i), BaseGeometry::EllMetric);
//		else
//			point = BaseGeometry::makePoint(pointsCHART.at(i), BaseGeometry::EucMetric);
//	}
//	return point;
//}

// ------------------------------------------------------------------------------------------------
// check if P is close to other collected points (excluding itself)
int CadTool::getClosestPoint(const OpenGL::Math::dvec3 &p)
{
	int k = -1; // return -1 if each point is outside a certain threshold
	
	const double minSize = _manager->pixelSize() * COLLAPSE_THRESHOLD;
	
	for( int i = _nPoints-1; i>=0; --i )
	{
		double size    = OpenGL::Math::distance(p,_points[i]);

		// p is the closest point IF:
		if( size < minSize   &&		// is the closest
			!_pointsLocked[i] &&	// is not locked
			i != _selIndex)			// is not itself
			
			k = i;
	}

	return k;
}


// ------------------------------------------------------------------------------------------------
// collect a point
void CadTool::collectPoint(const OpenGL::Math::dvec3 &p/*, bool firstPointDraggable*/)
{
	if( canStayOnScreen() && _completed )
		reset();
	
	switch(_status)
	{
	case Drag:
	case Idle:
		if( _nPoints == 0 )
		{
			setPoint(p);
			_capturedPoint = p;
			_selIndex      = -1;
		}
		else
			_selIndex = getClosestPoint(p);

		//if( _selIndex < 0 )
		//{
		//	_selIndex      = setPoint(p);
		//	_capturedPoint = p;
		//}
		//else
		//	_status = Drag;

		//if( _nPoints < _totPoints )
		//{
		//	_selIndex      = setPoint(p);
		//	_capturedPoint = p;

		//	if( _nPoints == 1 && _nPoints < _totPoints )
		//		
		//		_selIndex = -1; // prevent to set the first point as draggable
		//}
		//else
		//{
		//	_selIndex = getClosestPoint(p);
		//}
		break;
	
	case Complete:
		break;
	}

	//updateGUI = true;
	validate();
}

// ------------------------------------------------------------------------------------------------
// Drag selected point if any, insert and drag a new one otherwise
void CadTool::dragPoint(const OpenGL::Math::dvec3& point/*, bool firstPointDraggable*/)
{
	if (canStayOnScreen() && _completed)
		reset();

	switch(_status)
	{
	case Idle:
		if( _totPoints == 1 )/*_nPoints == 0 && _nPoints < _totPoints ) */ // dragging the first point
		{
			_selIndex = setPoint(point);
			
			//// call again setPoint in order to duplicate currPoint
			//// and be able to drag the second one
			//if( _totPoints > 1 && 
			//	_nPoints < _totPoints )
			//	_selIndex = setPoint(point);
		}
		else if( _nPoints < _totPoints )
		{
			if(_selIndex < 0)
				_selIndex = setPoint(point,true);
			else
				setPoint(point,false,_selIndex);
		}
		_status   = Drag;
		break;

	case Drag:
		if( _selIndex >= 0 )
			setPoint(point,false,_selIndex);

		_status = Drag;
		break;

	case Complete:
		_status = Idle;
		break;
	}

	//updateGUI = true;
	validate();
}


// ------------------------------------------------------------------------------------------------
// Done!
void CadTool::done(const OpenGL::Math::dvec3 &p, bool force)
{
	switch(_status)
	{
	case Idle:
		// already collected all needed points ?
		if(force)
		{
			for(int i=_nPoints;i<_totPoints;++i)
				setPoint(p,false,i);

			_nPoints = _totPoints;
		}

		if(_nPoints == _totPoints)
		{
			if(!isWaitingConfirm() || isChained() )
				_status = Complete;
		}
		break;

	case Drag:
		{
			_status = Idle;
			
			if( !OpenGL::Math::isinf( OpenGL::Math::length( _capturedPoint ) )  )
			{
				const double d = OpenGL::Math::distance(_capturedPoint,p);
				const int pxlDist = (int)(_manager->pixelSize() * d);

				if( pxlDist < DISCARD_THRESHOLD && _nPoints > 0)
					_points[_nPoints-1] = p;
			}

			//int closest = getClosestPoint(p);

			//// if already has a point
			//// that is too close to the new one (k>=0)
			//if( closest >= 0 && _selIndex >= 0)
			//	_nPoints = force ? _totPoints : _selIndex;	// discard p

			// reset selIndex
			//_selIndex = -1;
		
			// collected all needed points?
			if(_nPoints == _totPoints)
			{
				if(!isWaitingConfirm() || isChained() )
					_status = Complete;
			}
		}
		break;

	case Complete:
		break;
	}

	//updateGUI = true;
	
	validate();

	if(_status == Complete)
	{
		_completed = true;
		execute();
		if ( !canStayOnScreen() && !_chained )
			reset();
	
		// hilights the new inserted object
		//if( getType() == ToolType::Edit ) 
		//{
		//	std::tr1::shared_ptr<VObjectGL> obj = nullptr;
		//
		//	scene->drawForSelection();
		//	obj = scene->getObjectAtMousePosition();
		//	scene->hilightObject(obj);
		//}
	}
}

// ------------------------------------------------------------------------------------------------
// Done!
void CadTool::done()
{
	_nPoints   = _totPoints;

	validate();

	_status    = Complete;
	_completed = true;
	_chained   = false;

	execute();

	if( !canStayOnScreen() )
		reset();
	
	//for( size_t i=0; i<_pointsLocked.size(); ++i )
	//{
	//	if( !_pointsLocked[i] )
	//		_selIndex = i;
	//}
	
	// hilights the new inserted object
	//if( getType() == ActionType::Edit ) 
	//{
	//	std::tr1::shared_ptr<VObjectGL> obj = nullptr;
	//	
	//	scene->drawForSelection();
	//	obj = scene->getObjectAtMousePosition();
	//	scene->hilightObject(obj);
	//	//scene->computeSnapPoint();
	//}
}

//-------------------------------------------------------------------------------------------------
// Confirm
bool CadTool::confirm(const OpenGL::Math::dvec3 &p)
{
	if( !isWaitingConfirm() )
		return false;

	//bool r = false;
	//if( m.buttonDown() & QtFwk::MouseStatus::BUTTON_RIGHT )
	//{
	//	if( !m.isMoving() )
	//		_confirmPoint = p;
	//}
	//else if( m.buttonUp() & Fwk::MouseStatus::BUTTON_RIGHT )
	//{
	//	if( _confirmPoint.equals(p,IDSMath::TOLERANCE))
	//		r = true;
	//}

	return true;
}

//-------------------------------------------------------------------------------------------------
void CadTool::confirm()
{
	_waitConfirm = false;
}


//-------------------------------------------------------------------------------------------------
// Reset
void CadTool::reset()
{
	// backup current status before resetting
	//Status oldStatus = _status;

	_completed     = false;
	_chained       = false;
	//updateGUI	  = false;

	_nPoints       = 0;
	_selIndex      = -1;
	_status		   = Idle;
	_capturedPoint = OpenGL::Math::dvec3( std::numeric_limits<double>::infinity() );
	
	_pointsLocked.clear();	_pointsLocked.resize(_totPoints);
	_points.clear();		_points.resize(_totPoints);

	_attributes.clear();

	for( int i=0;i<_totPoints;++i )
	{
		_pointsLocked[i] = false;
		_points[i]		= OpenGL::Math::dvec3(0); 
	}

	// if we have a gui and the action has been completed
	//if( gui && oldStatus != Complete)
	//	gui->resetFields();
}

//-------------------------------------------------------------------------------------------------
void CadTool::chainPoint()
{
	_completed = false;
	_chained   = true;
	_status    = Idle;
	_selIndex  = _nPoints-1;
	_totPoints++;
	_points.resize( _totPoints );
	_pointsLocked.resize( _totPoints );
	_capturedPoint = OpenGL::Math::dvec3( std::numeric_limits<double>::infinity() );
}

//-------------------------------------------------------------------------------------------------
// Start the next action of the chain if this action is completed
//void CadTool::nextTool()
//{
//	if (_completed && _nextTool)
//		_manager->setCurrentTool(_nextTool);
//}

//-------------------------------------------------------------------------------------------------
//void CadTool::clearToolsChain()
//{
//	if( _nextTool )
//	{
//		_nextTool->clearToolsChain();
//		_nextTool->reset();
//		delete _nextTool;
//		_nextTool = NULL;
//	}
//	
//	//if( gui && sharedGUI )
//	//{
//	//	CustomProxy *p = getGUIItem();
//	//	if( p)
//	//	{
//	//		scene->removeItem(p);
//	//		delete p;
//	//	}
//	//	gui = NULL;
//	//}
//}

//-------------------------------------------------------------------------------------------------
void CadTool::lockPoint(int index, bool lock)
{
	_pointsLocked[index] = lock;
}

//-------------------------------------------------------------------------------------------------
void CadTool::lockAttribute(const std::string &attribName, double value)
{
	_attributes[attribName] = value;
}

//-------------------------------------------------------------------------------------------------
void CadTool::unlockAttribute(const std::string &attribName)
{
	Attributes::iterator it = _attributes.find(attribName);
	
	if( it != _attributes.end() )
		_attributes.erase(it);
}

//-------------------------------------------------------------------------------------------------
bool CadTool::isAttributeLocked(const std::string &attribName) const
{
	return (_attributes.find(attribName) != _attributes.end() );
}